using System;
using System.Collections.Generic;
using NHibernate.Criterion;

namespace Project.Library.NHibernate
{
    /// <summary>
    /// Implementation of the repository pattern
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="idT">The type of the d T.</typeparam>
    public class NHibernateRepository<T,idT> : NHibernateRepositoryBase, INHibernateRepository<T,idT> where T : class
    {
        /// <summary>
        /// Existses the specified criteria.
        /// </summary>
        /// <param name="criteria">The criteria.</param>
        /// <returns></returns>
        public virtual bool Exists(DetachedCriteria criteria)
        {
            return Count(criteria) > 0;
        }

        /// <summary>
        /// Counts the specified criteria.
        /// </summary>
        /// <param name="criteria">The criteria.</param>
        /// <returns></returns>
        public virtual long Count(DetachedCriteria criteria)
        {
            return Convert.ToInt64(criteria.SetProjection(Projections.RowCountInt64())
                                       .GetExecutableCriteria(Session)
                                       .UniqueResult());
        }

        /// <summary>
        /// Creates the query batcher.
        /// </summary>
        /// <returns></returns>
        public IQueryBatcher CreateQueryBatcher()
        {
            return new QueryBatcher(Session);
        }

        #region DetachedCriteria

        /// <summary>
        /// Creates the detached criteria.
        /// </summary>
        /// <returns></returns>
        public DetachedCriteria CreateDetachedCriteria()
        {
            return DetachedCriteria.For<T>();
        }

        /// <summary>
        /// Creates the detached criteria.
        /// </summary>
        /// <param name="alias">The alias.</param>
        /// <returns></returns>
        public DetachedCriteria CreateDetachedCriteria(string alias)
        {
            return DetachedCriteria.For(typeof(T), alias);
        }

        #endregion

        #region Search

        /// <summary>
        /// Returns the entity corresponding to the Id.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public virtual T Get(idT id)
        {
            return Session.Get<T>(id);
        }

        /// <summary>
        /// Returns the entity AND its all object graph corresponding to the Id.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public virtual T Load(idT id)
        {
            return Session.Load<T>(id);
        }

        /// <summary>
        /// Finds the entities that match the criteria.
        /// </summary>
        /// <param name="criteria">The criteria.</param>
        /// <returns></returns>
        public virtual IEnumerable<T> FindAll(DetachedCriteria criteria)
        {
            return criteria.GetExecutableCriteria(Session).List<T>();
        }

        /// <summary>
        /// Finds the entities that match the criteria and order the result.
        /// </summary>
        /// <param name="criteria">The criteria.</param>
        /// <param name="orders">The orders.</param>
        /// <returns></returns>
        public virtual IEnumerable<T> FindAll(DetachedCriteria criteria, params Order[] orders)
        {
            if (orders != null)
            {
                foreach (var order in orders)
                {
                    criteria.AddOrder(order);
                }
            }
            return FindAll(criteria);
        }

        /// <summary>
        /// Finds the entities that match the criteria and order the result.
        /// Gives support for paging
        /// </summary>
        /// <param name="criteria">The criteria.</param>
        /// <param name="firstResult">The first result.</param>
        /// <param name="maxResult">The max result.</param>
        /// <param name="orders">The orders.</param>
        /// <returns></returns>
        public virtual IEnumerable<T> FindAll(DetachedCriteria criteria, int firstResult, int maxResult, params Order[] orders)
        {
            criteria.SetFirstResult(firstResult).SetMaxResults(maxResult);
            return FindAll(criteria, orders);
        }

        /// <summary>
        /// Finds the result that match the criteria.
        /// Throw an exception if more than one entity matches the criteria
        /// </summary>
        /// <param name="criteria">The criteria.</param>
        /// <returns></returns>
        public virtual T FindOne(DetachedCriteria criteria)
        {
            return criteria.GetExecutableCriteria(Session).UniqueResult<T>();
        }

        /// <summary>
        /// Finds the first result that match the criteria.
        /// Returns null if no entity matches the criteria
        /// </summary>
        /// <param name="criteria">The criteria.</param>
        /// <returns></returns>
        public virtual T FindFirst(DetachedCriteria criteria)
        {
            var results = criteria.SetFirstResult(0).SetMaxResults(1).GetExecutableCriteria(Session).List<T>();
            return results.Count > 0 ? results[0] : null;
        }

        /// <summary>
        /// Finds the first.
        /// </summary>
        /// <param name="criteria">The criteria.</param>
        /// <param name="orders">The orders.</param>
        /// <returns></returns>
        public virtual T FindFirst(DetachedCriteria criteria, params Order[] orders)
        {
            foreach (var order in orders)
            {
                criteria.AddOrder(order);
            }
            return FindFirst(criteria);
        }

        #endregion

        #region Persistance

        /// <summary>
        /// Saves the or update.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        public virtual T SaveOrUpdate(T entity)
        {
            Session.SaveOrUpdate(entity);
            return entity;
        }

        /// <summary>
        /// Saves the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        public virtual T Save(T entity)
        {
            return (T) Session.Save(entity);
        }

        /// <summary>
        /// Updates the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        public virtual T Update(T entity)
        {
            Session.Update(entity);
            return entity;
        }

        /// <summary>
        /// Deletes the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public virtual void Delete(T entity)
        {
            Session.Delete(entity);
        }

        /// <summary>
        /// Deletes the specified criteria.
        /// </summary>
        /// <param name="criteria">The criteria.</param>
        public virtual void Delete(DetachedCriteria criteria)
        {
            foreach (var entity in FindAll(criteria))
            {
                Delete(entity);
            }
        }

        #endregion
    }
}